package de.jonasblatt.networkapi.rpi.network.server;

import java.awt.AWTException;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Timer;

import de.jonasblatt.networkapi.rpi.model.NetworkModel;
import de.jonasblatt.networkapi.rpi.model.RpiWorker;
import de.jonasblatt.networkapi.rpi.network.commands.ClientCommand;
import de.jonasblatt.networkapi.rpi.network.commands.Command;
import de.jonasblatt.networkapi.rpi.network.commands.server.SCJoinServerDenied;
import de.jonasblatt.networkapi.rpi.network.commands.server.SCSendCliendId;

public class ServerImpl implements Server {
	public final static int DEFAULTPORT = 5220;

	private int hostID;
	private int incClientIds;
	private int maxClients;
	private NetworkModel networkModel;
	private ArrayList<ServerWorker> observers;
	private int port;
	private String serverAdress;
	private ServerLog serverLog;
	private ServerSchedule serverScheduleTask;
	private ServerSocket serverSocket;
	private boolean started = false;
	private boolean stopFlag;

	public ServerImpl(int maxClients) {
		this.port = DEFAULTPORT;
		this.observers = new ArrayList<ServerWorker>();
		this.maxClients = maxClients;
		this.incClientIds = 0;
		this.serverLog = new ServerLog(this);
		this.setStarted(false);
		this.setServerAdress("");
	}

	public ServerImpl(int port, int maxClients) {
		this.port = port;
		this.observers = new ArrayList<ServerWorker>();
		this.maxClients = maxClients;
		this.incClientIds = 0;
		this.serverLog = new ServerLog(this);
		this.setStarted(false);
		this.setServerAdress("");
	}

	@Override
	public void closeConnection() {
		try {
			this.stopFlag = true;
			this.serverSocket.close();
			this.serverSocket = null;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private boolean createServerSocket() {
		try {
			System.out.println("Starting Server..");

			this.serverSocket = new ServerSocket(this.port);
			this.serverLog.addMessage("Server started. Listening on port "
					+ this.port);

		} catch (IOException e) {
			this.serverLog.addMessage("Could not listen on port " + this.port);
			return false;
		}
		this.setStarted(true);
		this.setServerAdress(this.serverSocket.getLocalSocketAddress()
				.toString());
		while (true) {
			if (this.stopFlag) {
				this.serverLog.addMessage("Server stopping..");
				this.setStarted(false);
				this.setServerAdress("");
				return true;
			}
			ServerWorker w = null;
			try {
				w = new ServerWorker(this.serverSocket.accept(), this,
						this.serverLog);
				this.serverLog.addMessage("New client. Checking id..");
				if (this.observers.size() >= this.maxClients) {
					w.update(new SCJoinServerDenied());
					this.serverLog
							.addMessage("Client joining denied. Max Clients connected!");
				} else {
					this.incClientIds++;
					w.setClientId(this.incClientIds);
					w.update(new SCSendCliendId(w.getClientId()));
					this.serverLog
							.addMessage("Client joining accepted. Client id: "
									+ w.getClientId());
					this.observers.add(w);
					Thread t = new Thread(w);
					t.start();
				}
			} catch (IOException e) {
				this.serverLog.addMessage("Accept failed: " + 655);
			}
		}
	}

	/**
	 * @return the hostID
	 */
	@Override
	synchronized public int getHostID() {
		return this.hostID;
	}

	public String getLog() {
		return "";
	}

	@Override
	synchronized public NetworkModel getModel() {
		return this.networkModel;
	}

	/**
	 * @return the port
	 */
	synchronized public int getPort() {
		return this.port;
	}

	/**
	 * @return the map
	 */

	synchronized public String getServerAdress() {
		return this.serverAdress;
	}

	public ServerSchedule getServerScheduleTask() {
		return this.serverScheduleTask;
	}

	synchronized public boolean isStarted() {
		return this.started;
	}

	@Override
	public void notify(ClientCommand cmd) {
		this.serverScheduleTask.offerCommand(cmd);
	}

	public void register(ServerWorker observer) {
		this.observers.add(observer);
	}

	@Override
	public void remove(ServerWorker observer) {
		this.observers.remove(observer);
	}

	@Override
	public void run() {
		Timer timer = new Timer();
		timer.scheduleAtFixedRate(this.serverScheduleTask, 500, 50);
		this.createServerSocket();
		timer.cancel();
		timer = null;
	}

	/**
	 * @param hostID
	 *            the hostID to set
	 */
	@Override
	synchronized public void setHostID(int hostID) {
		this.hostID = hostID;
	}

	@Override
	synchronized public void setModel(NetworkModel model) {
		// TODO Auto-generated method stub
		this.networkModel = model;
	}

	/**
	 * @param map
	 *            the map to set
	 */

	private void setServerAdress(String serverAdress) {
		this.serverAdress = serverAdress;
	}

	/**
	 * Sets the task, which is scheduled in a timed interval, only if the server
	 * has not created a connection yet.
	 *
	 * @param task
	 */
	public void setServerScheduleTask(ServerSchedule task) {
		if (this.serverSocket == null) {
			this.serverScheduleTask = task;
		}
	}

	synchronized private void setStarted(boolean started) {
		this.started = started;
	}

	public boolean start() {
		try {
			this.serverLog.addMessage("Creating model..");
			this.networkModel = RpiWorker.getInstance();
		} catch (AWTException e) {
			this.serverLog.addMessage("Creating model failed.");
			return false;
		}

		this.serverScheduleTask = new ServerSchedule(this);
		this.stopFlag = false;

		this.hostID = 1;

		// start Server tasks
		Thread t = new Thread(this);
		t.start();
		return true;
	}

	@Override
	public void updateAll(Command msg) {
		for (ServerWorker ss : this.observers) {
			ss.update(msg);
		}
	}

	@Override
	public boolean updateClient(int clientID, Command msg) {
		for (ServerWorker ss : this.observers) {
			synchronized (ss) {
				if (ss.getClientId() == clientID) {
					return ss.update(msg);
				}
			}
		}
		return false;
	}
}
